home *** CD-ROM | disk | FTP | other *** search
/ Celestin Apprentice 7 / Apprentice-Release7.iso / Source Code / Pascal / Code Resources / Eclectic CDEFs / Gauss CDEF Folder / NeoTextBox.p < prev   
Text File  |  1997-02-25  |  19KB  |  416 lines

  1. {    NeoTextBox: Faster, script-aware alternative to TETextBox    }
  2. {}
  3. {    Originally written in C by Bryan K. Ressler, from Develop issue 9    }
  4. {    Pascal conversion by Peter N. Lewis    }
  5.  
  6.  
  7. {    Modifications by Sebastiano Pilla    }
  8. {}
  9. {    1)    Added optional saving/restoring of the clip region. This is controlled by the inSwapClipping parameter: if set to    }
  10. {        TRUE, then NeoTextBox saves the current clip region, clips its drawing to inWrapBox, then restores the previously    }
  11. {        saved clip region at the end of the drawing; if set to FALSE, then NeoTextBox uses whatever clip region is current    }
  12. {}
  13. {    2)    Added a check to avoid drawing text outside of inWrapBox, because it will be clipped anyway. However, the entire text    }
  14. {        must still be traversed to determine the correct line breaks    }
  15. {}
  16. {    3)    Added a special one-line case, to avoid the wrapping loop where it is known that the text fits all into the given box }
  17.  
  18. unit NeoTextBox;
  19.  
  20.  
  21. interface
  22.  
  23.  
  24.     uses
  25.         Types;
  26.  
  27.  
  28.     const
  29.         ntbJustFull = 128;
  30.  
  31.  
  32. {    NeoTextBox    }
  33. {}
  34. {    Word-wraps text inside a given box    }
  35. {}
  36. {    Entry:    inTextPtr = pointer to the text we need to wrap    }
  37. {            inTextLen = length (in bytes) of the text    }
  38. {            inWrapBox = the box within which we're wrapping }
  39. {            inAlign = the text alignment    }
  40. {                        teForceLeft, teFlushLeft -> left justified    }
  41. {                        teJustCenter, teCenter -> center justified    }
  42. {                        teJustRight, teFlushRight -> right justified    }
  43. {                        ntbJustFull -> full justified    }
  44. {                        teJustLeft, teFlushDefault -> system justified    }
  45. {            inLhCode = line height code    }
  46. {                        < 0 -> variable - based on tallest character    }
  47. {                        0 -> default - based on GetFontInfo    }
  48. {                        > 0 -> fixed - use inLhCode as the line height    }
  49. {            inSwapClipping = TRUE if NeoTextBox should save and restore the clipping, FALSE if this is unnecessary    }
  50. {    Exit:    outEndY = vertical coordinate of the last line, if non-zero    }
  51. {            outLhUsed = line height used to draw the text, if non-zero    }
  52. {            function result = total number of lines drawn (even outside of the given box)    }
  53.     function NeoTextBox (inTextPtr: Ptr;
  54.                                     inTextLen: UInt32;
  55.                                     inWrapBox: Rect;
  56.                                     inAlign: SInt16;
  57.                                     inLhCode: SInt16;
  58.                                     var outEndY: SInt16;
  59.                                     var outLhUsed: SInt16;
  60.                                     inSwapClipping: Boolean): SInt16;
  61.  
  62.  
  63. implementation
  64.  
  65.  
  66.     uses
  67.         Fonts, FixMath, QuickDrawText, Script, TextUtils;
  68.  
  69.  
  70.     const
  71.         kReturnChar = $D;
  72.  
  73.  
  74. {    Max    }
  75. {}
  76. {    Returns the maximum between the two given numbers    }
  77. {}
  78. {    Entry:    inNum1 = first number    }
  79. {            inNum2 = second number    }
  80. {    Exit:    function result = maximum between inNum1 and inNum2    }
  81.     function Max (inNum1, inNum2: SInt32): SInt32;
  82.     inline
  83.         $201F, $2E9F, $B097, $6F02, $2E80;
  84.  
  85.  
  86. {    AddPtrLong    }
  87. {}
  88. {    Adds an offset to the given pointer    }
  89. {}
  90. {    Entry:    inPtr = pointer (first operand)    }
  91. {            inOffset = offset (second operand) to be added to inPtr    }
  92. {    Exit:    function result = pointer to Ord4(inPtr) + inOffset    }
  93.     function AddPtrLong (inPtr: univ Ptr;
  94.                                     inOffset: SInt32): Ptr;
  95.     inline
  96.         $201F,    { move.l    (sp)+,d0    ; pop inOffset }
  97.         $D09F,    { add.l        (sp)+,d0    ; add inPtr to inOffset (and pop inPtr) }
  98.         $2E80;    { move.l    d0,(sp)        ; place in result }
  99.  
  100.  
  101. {     NTBLineHeight    }
  102. {}
  103. {    Figures line height    }
  104. {}
  105. {    Entry:    inTextPtr = entire text given to NeoTextBox    }
  106. {            inTextLen = length (in bytes) of the text    }
  107. {            inWrapBox = box within we're wrapping    }
  108. {            inLhCode = line height code given to NeoTextBox    }
  109. {    Exit:    outStartY = starting vertical pen location    }
  110. {            function result = line height to use    }
  111.     function NTBLineHeight (inTextPtr: Ptr;
  112.                                     inTextLen: UInt32;
  113.                                     inWrapBox: Rect;
  114.                                     inLhCode: SInt16;
  115.                                     var outStartY: SInt16): UInt16;
  116.         var
  117.             fInfo: FontInfo;                        { old-style font information record }
  118.             frac: Point;                            { fraction for the TrueType calls }
  119.             asc, desc: SInt16;                    { used in the OutlineMetrics calls }
  120.             lineHeight: UInt16;                    { return value }
  121.             err: OSErr;
  122.     begin
  123.  
  124.         GetFontInfo(fInfo);
  125.         if inLhCode < 0 then
  126.             begin
  127.  
  128.         { -------------------------------------------------------------------------------------------------- }
  129.         { If the user has specified variable-height lines, we need to try to determine the tallest ascent in the given text. }
  130.         { We can only really do this if the font is a TrueType font.  Otherwise, we punt and use }
  131.         { old - fashioned GetFontInfo numbers . }
  132.         { -------------------------------------------------------------------------------------------------- }
  133.  
  134.                 frac.h := 1;
  135.                 frac.v := 1;
  136.                 if IsOutline(frac, frac) then
  137.                     begin
  138.  
  139.             { ---------------------------------------------------------------------------------------------- }
  140.             { At this Point we know the current font is a TrueType font, so we do an OutlineMetrics call with our full text. }
  141.             { It will put the tallest character ascent into asc, and the deepest descent into desc. Then we choose between }
  142.             { whichever's most between the old-style ascent/descent and the numbers we get from the OutlineMetrics call. }
  143.             { ---------------------------------------------------------------------------------------------- }
  144.  
  145.                         err := OutlineMetrics(UInt16(inTextLen), inTextPtr, frac, frac, asc, desc, nil, nil, nil);
  146.                         lineHeight := Max(fInfo.ascent, asc) + Max(fInfo.descent, -desc) + fInfo.leading;
  147.                         outStartY := inWrapBox.top + Max(fInfo.ascent, asc) + fInfo.leading;
  148.                     end
  149.  
  150.                 else
  151.                     begin
  152.  
  153.             { ------------------------------------------------------------------------------------------------- }
  154.             { At this Point we know the current font isn't TrueType, so we just use the old way of calculating line height. }
  155.             { ------------------------------------------------------------------------------------------------- }
  156.  
  157.                         lineHeight := Max(fInfo.ascent, asc) + Max(fInfo.descent, -desc) + fInfo.leading;
  158.                         outStartY := inWrapBox.top + Max(fInfo.ascent, asc) + fInfo.leading;
  159.                     end;
  160.             end
  161.  
  162.         else if inLhCode = 0 then
  163.             begin
  164.  
  165.         { ---------------------------------------------------------------------------------------------------- }
  166.         { If the user has specified "default" line height, he just wants us to get the line height from the FontInfo record.    }
  167.         { ---------------------------------------------------------------------------------------------------- }
  168.  
  169.                 lineHeight := fInfo.ascent + fInfo.descent + fInfo.leading;
  170.                 outStartY := inWrapBox.top + fInfo.ascent + fInfo.leading;
  171.  
  172.             end
  173.         else
  174.             begin
  175.  
  176.         { ----------------------------------------------------------------------------------------------------- }
  177.         { If the user has provided a specific line height, we just trust them.  We can't really generate too good of a }
  178.         { starting vertical coordinate, but we munge one together anyway. }
  179.         { ----------------------------------------------------------------------------------------------------- }
  180.  
  181.                 lineHeight := inLhCode;
  182.                 outStartY := inWrapBox.top + inLhCode + fInfo.leading;
  183.  
  184.             end;
  185.  
  186.         NTBLineHeight := lineHeight;
  187.     end;
  188.  
  189.  
  190. {    NTBDraw    }
  191. {}
  192. {    Draws a line with appropriate justification    }
  193. {}
  194. {    Entry:    inLineStartPtr = pointer to the beginning of the text for the current line    }
  195. {            inLineBytes = length (in bytes) of the text for this line    }
  196. {            inWrapBox = box within which we're wrapping    }
  197. {            inAlign = text alignment as specified by the user    }
  198. {            inCurY = our current vertical pen coordinate    }
  199. {            inBoxWidth = width of inWrapBox (since NeoTextBox already calculated it)    }
  200. {            inBreakCode = break code returned from StyledLineBreak    }
  201.     procedure NTBDraw (inLineStartPtr: Ptr;
  202.                                     inLineBytes: SInt32;
  203.                                     inWrapBox: Rect;
  204.                                     inAlign, inCurY, inBoxWidth: SInt16;
  205.                                     inBreakCode: StyledLineBreakCode);
  206.         var
  207.             blackLen: UInt32;    { length of non-white characters }
  208.             slop: SInt16;        { number of pixels of slop for full just }
  209.     begin
  210.  
  211.     { ------------------------------------------------------------------------------------------------------------ }
  212.     { The first thing we do here is determine the length of the "black" part of the current line.  This excludes spaces, carriage }
  213.     { returns, and other white stuff depending on the language.  How do we know what to eliminate?  We DON'T!  So we ask our }
  214.     { friend the Script Manager to do it for us.  VisibleLength returns the number of bytes that we should use for pixel width }
  215.     { calculations. }
  216.     { ------------------------------------------------------------------------------------------------------------ }
  217.  
  218.         blackLen := VisibleLength(inLineStartPtr, inLineBytes);
  219.  
  220.         if inAlign = ntbJustFull then
  221.             begin
  222.  
  223.         { --------------------------------------------------------------------------------------------------------- }
  224.         { For full justification, we need to calculate the "slop" space on the line that's not filled up by the text.  Then we }
  225.         { move to the margin and get ready to draw BUT WAIT! If this is the last line of a paragraph, we need to draw it with }
  226.         { whatever the system justification is. So if the break code indicates we're at the end of the text, or we can find a }
  227.         { carriage return there ourselves, we just change the text alignment to the current default system text alignment and }
  228.         { fall through without drawing. If it's just another line within a paragraph, we use the handy Script Manager routine }
  229.         { DrawJust to draw it justified. In languages like Arabic, full justification is performed differently than just spacing }
  230.         { out the words. DrawJust handles cases like these correctly. }
  231.         { Note that when we go looking for the carriage return at the end of the line, it's okay to simply index to the last Byte }
  232.         { of the string. This is because the carriage return character, 0x0d, is in the control-code range, which is defined to }
  233.         { never be the low Byte of a two Byte character. }
  234.         { --------------------------------------------------------------------------------------------------------- }
  235.  
  236.                 slop := inBoxWidth - TextWidth(inLineStartPtr, 0, blackLen);
  237.                 MoveTo(inWrapBox.left, inCurY);
  238.                 if (inBreakCode = smBreakOverflow) | (AddPtrLong(inLineStartPtr, inLineBytes - 1)^ = kReturnChar) then
  239.                     inAlign := GetSysDirection
  240.                 else
  241.                     DrawJust(inLineStartPtr, blackLen, slop);
  242.             end;
  243.  
  244.     { ------------------------------------------------------------------------------------------------------------ }
  245.     { For the rest of the text alignments (left, center, and right), we just move the pen to the right place and draw the text }
  246.     { with DrawText.  Note that the alignments that could have come into the NeoTextBox call have been resolved down to one }
  247.     { of the four constants teForceLeft, teJustRight, teJustCenter, or ntbJustFull. }
  248.     { ------------------------------------------------------------------------------------------------------------ }
  249.  
  250.         case inAlign of
  251.             teForceLeft, teJustLeft: 
  252.                 MoveTo(inWrapBox.left, inCurY);
  253.             teJustRight: 
  254.                 MoveTo(inWrapBox.right - TextWidth(inLineStartPtr, 0, blackLen), inCurY);
  255.             teJustCenter: 
  256.                 MoveTo(inWrapBox.left + (inBoxWidth - TextWidth(inLineStartPtr, 0, blackLen)) div 2, inCurY);
  257.             otherwise
  258.                 ;
  259.         end;
  260.  
  261.         if inAlign <> ntbJustFull then
  262.             DrawText(inLineStartPtr, 0, inLineBytes);
  263.     end;
  264.  
  265.  
  266. {    NeoTextBox    }
  267. {}
  268. {    Word-wraps text inside a given box    }
  269. {}
  270. {    Entry:    inTextPtr = pointer to the text we need to wrap    }
  271. {            inTextLen = length (in bytes) of the text    }
  272. {            inWrapBox = the box within which we're wrapping }
  273. {            inAlign = the text alignment    }
  274. {            inLhCode = line height code    }
  275. {            inSwapClipping = TRUE if NeoTextBox should save and restore the clipping    }
  276. {    Exit:    outEndY = vertical coordinate of the last line, if non-zero    }
  277. {            outLhUsed = line height used to draw the text, if non-zero    }
  278. {            function result = total number of lines drawn (even outside of the given box)    }
  279.     function NeoTextBox (inTextPtr: Ptr;
  280.                                     inTextLen: UInt32;
  281.                                     inWrapBox: Rect;
  282.                                     inAlign: SInt16;
  283.                                     inLhCode: SInt16;
  284.                                     var outEndY: SInt16;
  285.                                     var outLhUsed: SInt16;
  286.                                     inSwapClipping: Boolean): SInt16;
  287.         var
  288.             oldClip: RgnHandle;                        { saved clipping region }
  289.             lineStartPtr: Ptr;                        { pointer to beginning of a line }
  290.             textEndPtr: Ptr;                        { pointer to the end of input text }
  291.             fixedMax: Fixed;                        { boxWidth converted to fixed point }
  292.             wrapWid: Fixed;                        { width to wrap to }
  293.             lineBytes: SInt32;                        { number of bytes in one line }
  294.             textLeft: UInt32;                        { pointer to remaining bytes of text }
  295.             boxWidth: SInt16;                        { width of the wrapBox }
  296.             lineHeight: UInt16;                        { calculated line height }
  297.             curY: SInt16;                            { current vertical pen location }
  298.             lineCount: UInt16;                        { number of lines we've drawn }
  299.             breakCode: StyledLineBreakCode;        { returned code from StyledLineBreak }
  300.     begin
  301.  
  302.     { ---------------------------------------------------------------------------------------------------------- }
  303.     { Check the parameters we're given, and exit immediately if they're wrong. }
  304.     { ---------------------------------------------------------------------------------------------------------- }
  305.         if (inTextPtr = nil) or (inTextLen = 0) then
  306.             Exit(NeoTextBox);
  307.  
  308.     { ---------------------------------------------------------------------------------------------------------- }
  309.     { First, we save the old clipping region and clip to wrapBox, if requested. Then, figure the width of wrapBox, and make }
  310.     { a fixed point version of it. Also, resolve the text alignment teFlushDefault to be the default system text alignment. }
  311.     { ---------------------------------------------------------------------------------------------------------- }
  312.  
  313.         if inSwapClipping then
  314.             begin
  315.                 oldClip := NewRgn;
  316.                 GetClip(oldClip);
  317.                 ClipRect(inWrapBox);
  318.             end;
  319.         boxWidth := inWrapBox.right - inWrapBox.left;
  320.         if inAlign = teFlushDefault then
  321.             inAlign := GetSysDirection;
  322.  
  323.     { ---------------------------------------------------------------------------------------------------------- }
  324.     { Now we call NTBLineHeight to calculate the appropriate line height. It also figures our starting vertical pen location in }
  325.     { curY based on the line height and other metric parameters. Clear lineCount, set lineStartPtr to point to the beginning of }
  326.     { the text, calculate textEnd for comparison to know when we're done, and preset textLeft to be all the text. }
  327.     { ---------------------------------------------------------------------------------------------------------- }
  328.  
  329.         lineHeight := NTBLineHeight(inTextPtr, inTextLen, inWrapBox, inLhCode, curY);
  330.         lineCount := 0;
  331.         lineStartPtr := inTextPtr;
  332.         textEndPtr := AddPtrLong(inTextPtr, inTextLen);
  333.         textLeft := inTextLen;
  334.  
  335.     { ------------------------------------------------------------------------------------------------------------ }
  336.     { Special one-line case: if the width of the input text is less than boxWidth, we avoid the wrapping loop and call NTBDraw }
  337.     { directly to draw the single line with the appropriate justification. }
  338.     { Note that the TextWidth call uses a 16-bit value to specify the number of bytes, where the inTextLen parameter given }
  339.     { to NeoTextBox is a 32-bit value. So, we hope that the width of inWrapBox is small enough to let us correctly handle this }
  340.     { special case. }
  341.     { ------------------------------------------------------------------------------------------------------------ }
  342.         if boxWidth > TextWidth(inTextPtr, 0, LoWrd(inTextLen)) then
  343.             begin
  344.                 NTBDraw(inTextPtr, inTextLen, inWrapBox, inAlign, curY, boxWidth, smBreakOverflow);
  345.                 textLeft := 0;                    { avoids entering the wrapping loop }
  346.             end;
  347.  
  348.     { ------------------------------------------------------------------------------------------------------------ }
  349.     { This is the main wrap-and-draw loop. I bet you never thought wrapping text could be so easy... }
  350.     { ------------------------------------------------------------------------------------------------------------ }
  351.  
  352.         fixedMax := Long2Fix(boxWidth);
  353.         while textLeft > 0 do
  354.             begin
  355.  
  356.         { ------------------------------------------------------------------------------------------------------- }
  357.         { Every line, we have to preset lineBytes to something non-zero. This tells StyledLineBreak that we're drawing the }
  358.         { first format run on the line (of course, for us, there's only ONE format run total).  Also preset wrapWid. }
  359.         { StyledLineBreak will always modify lineBytes (to tell you how many bytes are on this line), and will modify }
  360.         { wrapWid, so we have to reset them each line. }
  361.         { ------------------------------------------------------------------------------------------------------- }
  362.  
  363.                 lineBytes := 1;
  364.                 wrapWid := fixedMax;
  365.  
  366.                 breakCode := StyledLineBreak(lineStartPtr, textLeft, 0, textLeft, 0, wrapWid, lineBytes);
  367.  
  368.         { ------------------------------------------------------------------------------------------------------ }
  369.         { Now that the Script Manager has done all the really hard work for us, we draw the line. We already knew lineStart, }
  370.         { StyledLineBreak gave us lineBytes, which we pass to NTBDraw. It'll handle the different text alignments itself. }
  371.         { Note that we really shouldn't draw text outside inWrapBox, but nonetheless it is necessary to break the entire text }
  372.         { into lines, to have the correct line breaks: this means that we call NTBDraw only if curY <= inWrapBox.bottom + lineHeight }
  373.         { ------------------------------------------------------------------------------------------------------ }
  374.  
  375.                 if curY <= inWrapBox.bottom + lineHeight then
  376.                     NTBDraw(lineStartPtr, lineBytes, inWrapBox, inAlign, curY, boxWidth, breakCode);
  377.  
  378.         { ------------------------------------------------------------------------------------------------------ }
  379.         { Now we advance our vertical position down by the height of one line, advance lineStartPtr by the number of bytes }
  380.         { we just drew, calculate a new textLeft, and increment our line count. }
  381.         { ------------------------------------------------------------------------------------------------------ }
  382.  
  383.                 curY := curY + lineHeight;
  384.                 lineStartPtr := AddPtrLong(lineStartPtr, lineBytes);
  385.                 textLeft := textLeft - lineBytes;
  386.                 lineCount := lineCount + 1;
  387.  
  388.             end;
  389.  
  390.     { --------------------------------------------------------------------------------------------------------- }
  391.     { Well that was a job well done.  Let's return some useful values, too. Stuff our ending vertical coordinate and the line }
  392.     { height we calculated into outEndY and outLHUsed, respectively. These allow the guy to put something after the }
  393.     { wrapped text (or at least know the right place to put it). }
  394.     { --------------------------------------------------------------------------------------------------------- }
  395.  
  396.         outEndY := curY - lineHeight;
  397.         outLhUsed := lineHeight;
  398.  
  399.     { --------------------------------------------------------------------------------------------------------- }
  400.     { Finally, restore the clipping region (if requested), dispose of the region, and return the TOTAL number of lines drawn }
  401.     { (note that we didn't stop drawing when curY advanced past inWrapBox->bottom. This way, the user could tell that the text }
  402.     { overflowed inWrapBox. If you want to know how many lines fit, divide inWrapBox by outLhUsed. This way, you get the }
  403.     { best of both worlds. }
  404.     { --------------------------------------------------------------------------------------------------------- }
  405.  
  406.         if inSwapClipping then
  407.             begin
  408.                 SetClip(oldClip);
  409.                 DisposeRgn(oldClip);
  410.             end;
  411.  
  412.         NeoTextBox := lineCount;
  413.     end;
  414.  
  415.  
  416. end.